﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using System.Diagnostics;
using System.Windows.Data;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Text;
using System.Reflection;

namespace Bling.Util {
  public struct IntPoint {
    public int X;
    public int Y;

  }

  public static class MyMath {
    public static long Factorial(int n) {
      if (n <= 0) return 1;
      else return n * Factorial(n - 1);
    }
    private static readonly long[] FactTable = new long[20];
    static MyMath() {
      Func<int,long> Fact = null;
      int j = 0;
      Fact = (i) => {
        if (i <= 1) return 1;
        else if (i < j) return FactTable[j];
        else return i * Fact(i - 1);
      };
      for (j = 0; j < FactTable.Length; j++) FactTable[j] = Fact(j);
    }
  }

  public class MockArray<INDEX, ITEM> {
    public Func<INDEX, ITEM> Get { get; set; }
    public Action<INDEX, ITEM> Set { get; set; }
    public int Count;
    public ITEM this[INDEX idx] {
      get { return Get(idx); }
      set { Set(idx, value); }
    }
  }


  public delegate void TypedDependencyPropertyChangedEventHandler<OWNER, TYPE>(OWNER owner, TYPE oldValue, TYPE newValue) where OWNER : DependencyObject;

  public interface ListChangeHandler<TYPE> {
    PropertyChangedEventHandler PropertyChangedEventHandler { get; }
    NotifyCollectionChangedEventHandler NotifyCollectionChangedEventHandler { get; }
    void addAll(IList<TYPE> list);
    void removeAll(IList<TYPE> list);
  }

  public interface ISimpleList<T> {
    T this[int index] { get; }
    int Count { get; }
  }
  public abstract class SimpleList<T> : ISimpleList<T> {
    public abstract T this[int index] { get; }
    public abstract int Count { get; }
    public static implicit operator SimpleList<T>(T[] list) { return new ListToSimple<T>(list); }
  }
  public class DefaultSimpleList<T> :  SimpleList<T> {
    public override int Count {
      get { return Count0(); }
    }
    public override T this[int index] {
      get { return F(index); }
    }
    private readonly Func<int> Count0;
    private readonly Func<int, T> F;
    public DefaultSimpleList(Func<int> Count, Func<int, T> F) {
      Count0 = Count;
    }
  }

  public class ListToSimple<T> : SimpleList<T> {
    readonly IList<T> Underlying;
    public ListToSimple(IList<T> Underlying) { this.Underlying = Underlying; }
    public override int Count {
      get { return Underlying.Count; }
    }
    public override T this[int index] {
      get { return Underlying[index]; }
    }
    public override string ToString() {
      return Underlying.ToString();
    }
  }

  public class MapList<T, S> : ISimpleList<S> {
    readonly ISimpleList<T> Underlying;
    readonly Func<T, S> f;
    public MapList(IList<T> Underlying, Func<T, S> F) : this(new ListToSimple<T>(Underlying), F) { }
    public MapList(ISimpleList<T> Underlying, Func<T, S> f) {
      this.Underlying = Underlying;
      this.f = f;
    }
    public S this[int index] { get { return f(Underlying[index]); } }
    public int Count { get { return Underlying.Count; } }
  }
  public class AList<T, S> : ISimpleList<S> {
    readonly T[] Underlying;
    readonly Func<T, S> f;
    readonly int Start;
    readonly int Count0;
    public AList(T[] Underlying, int Start, int Count, Func<T, S> f) {
      this.Underlying = Underlying;
      this.f = f;
      this.Start = Start;
      this.Count0 = Count;
    }
    public AList(T[] Underlying, Func<T, S> f) : this(Underlying, 0, Underlying.Length, f) { }
    public S this[int index] { get { return f(Underlying[Start + index]); } }
    public int Count { get { return Count0; } }
  }
  public static class Extensions {
    public static void Assert(this bool value) {
      if (!value) {
        Debug.Assert(false);
      }
    }
    public static ImageSource ImageFor(this string relativeFile, Type From) {
      return (new System.Windows.Media.Imaging.BitmapImage(relativeFile.MakePackUri(From)));
    }
    public static ImageSource[] ImageFor(this string path, Type From, params string[] args) {
      var ret = new ImageSource[args.Length];
      for (int i = 0; i < args.Length; i++) {
        string relativeFile = path + "/" + args[i];
        ret[i] = (new System.Windows.Media.Imaging.BitmapImage(relativeFile.MakePackUri(From)));
      }
      return ret;
    }

    public static Uri MakePackUri(this string relativeFile, Type From) {
      StringBuilder uriString = new StringBuilder();
      uriString.Append("pack://application:,,,");
      uriString.Append("/" + AssemblyShortName(From) + ";component/" + relativeFile);
      return new Uri(uriString.ToString(), UriKind.RelativeOrAbsolute);
    }
    private static string AssemblyShortName(Type From) {
      Assembly a = From.Assembly;

      // Pull out the short name.
      return a.ToString().Split(',')[0];
    }

    public static double Clamp(this double at, double min, double max) {
      if (at < min) at = min;
      if (at > max) at = max;
      return at;
    }
    public static float Clamp(this float at, float min, float max) {
      if (at < min) at = min;
      if (at > max) at = max;
      return at;
    }
    public static int Clamp(this int at, int min, int max) {
      if (at < min) at = min;
      if (at > max) at = max;
      return at;
    }
    public static Point Clamp(this Point at, Point min, Point max) {
      return new Point(at.X.Clamp(min.X, max.X), at.Y.Clamp(min.Y, max.Y));
    }

    public static Color Clamp(this Color at, Color min, Color max) {
      return Color.FromScRgb(at.ScA.Clamp(min.ScA, max.ScA),
                             at.ScR.Clamp(min.ScR, max.ScR),
                             at.ScG.Clamp(min.ScG, max.ScG),
                             at.ScB.Clamp(min.ScB, max.ScB));
                             

    }


    public static Point Size(this ImageSource source) { return new Point(source.Width, source.Height); }
    public static Point PairWiseDivide(this Point point, Point op) { return new Point(point.X / op.X, point.Y / op.Y); }
    public static Point PairWiseMultiply(this Point point, Point op) { return new Point(point.X * op.X, point.Y * op.Y); }

    public static System.Windows.Media.Imaging.BitmapSource LoadBitmap(this System.Drawing.Bitmap source) {
      return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(source.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
          System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
    }


    public static Vector Delta(this DragDeltaEventArgs source) {
      return new Vector(source.HorizontalChange, source.VerticalChange);
    }
    public static Orientation Reverse(this Orientation dir) {
      if (dir == Orientation.Horizontal) return Orientation.Vertical;
      else return Orientation.Horizontal;
    }

    public static double Select(this Vector vector, Orientation dir) {
      if (dir == Orientation.Horizontal) return vector.X;
      else return vector.Y;
    }
    public static double Select(this Point vector, Orientation dir) {
      if (dir == Orientation.Horizontal) return vector.X;
      else return vector.Y;
    }
    public static void Select(this Point vector, Orientation dir, double value) {
      if (dir == Orientation.Horizontal) vector.X = value;
      else vector.Y = value;
    }
    public static Vector Vector(this Orientation dir, double x) {
      if (dir == Orientation.Horizontal) return new Vector(x, 0);
      else return new Vector(0, x);
    }
    public static Point Point(this Orientation dir, double x) {
      if (dir == Orientation.Horizontal) return new Point(x, 0);
      else return new Point(0, x);
    }
    public static bool IsNan(this double d) {
      return double.IsNaN(d);
    }
    public static bool IsNan(this Point d) {
      return d.X.IsNan() || d.Y.IsNan();
    }
    public static bool IsNan(this Point3D d) {
      return d.X.IsNan() || d.Y.IsNan() || d.Z.IsNan();
    }

    public static double Square(this double t) { return t * t; }
    public static double Pow(this double t, double e) { return Math.Pow(t, e); }
    public static double Sqrt(this double t) { return Math.Sqrt(t); }

    public static readonly Color[] ColorsSelect = new Color[] {
      Colors.Red, 
      Colors.Blue, 
      Colors.Green,
      Colors.Violet, 
      Colors.Purple, 
      Colors.Orange, 
      Colors.Yellow, 
      Colors.MidnightBlue,
      Colors.SaddleBrown,
      Colors.DarkBlue,
      Colors.DarkViolet,
      Colors.DarkRed,
      Colors.DarkGreen, 
      Colors.RoyalBlue, 
      Colors.Tomato,
      Colors.Pink, Colors.Peru,
      Colors.Silver, Colors.Turquoise, 
      Colors.RosyBrown, Colors.PowderBlue,
      Colors.PeachPuff, 
      Colors.Black,
    };
    /// <summary>
    /// Select a fixed color based on an integer index. 
    /// </summary>
    /// <param name="idx">Index of color to select.</param>
    /// <returns>An arbitrary color associated with the index.</returns>
    public static Color SelectColor(this int idx) {
      return ColorsSelect[idx % ColorsSelect.Length];

    }
    public static T Convert<T>(this string str) {
      return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(str);
    }


    public static Point Max(this Point self, params Point[] others) {
      foreach (var other in others) {
        self = new Point(Math.Max(self.X, other.X), Math.Max(self.Y, other.Y));
      }
      return self;
    }
    public static Vector AbsMax(this Vector self, Vector other) {
      if (Math.Abs(other.X) > Math.Abs(self.X)) self.X = other.X;
      if (Math.Abs(other.Y) > Math.Abs(self.Y)) self.Y = other.Y;
      return self;
    }
    public static Point Min(this Point self, params Point[] others) {
      foreach (var other in others) {
        self = new Point(Math.Min(self.X, other.X), Math.Min(self.Y, other.Y));
      }
      return self;
    }
    public static Point Abs(this Point self) {
      return new Point(Math.Abs(self.X), Math.Abs(self.Y));
    }
    public static Vector Max(this Vector self, Vector other) {
      return new Vector(Math.Max(self.X, other.X), Math.Max(self.Y, other.Y));
    }
    public static Vector Min(this Vector self, Vector other) {
      return new Vector(Math.Min(self.X, other.X), Math.Min(self.Y, other.Y));
    }
    /*
    public static Vector scale(this Vector self) {
      if (Math.Abs(self.X) > Math.Abs(self.Y)) return self / Math.Abs(self.X);
      else return self / Math.Abs(self.Y);
    }
     */

    public static Vector Abs(this Vector self) {
      return new Vector(Math.Abs(self.X), Math.Abs(self.Y));
    }
    public static Point Inflect(this Point self, Point with) {
      var dx = self.X;
      var dy = self.Y;
      if (dx > with.X) dx = (2 * with.X) - dx;
      if (dy > with.Y) dy = (2 * with.Y) - dy;
      return new Point(dx, dy);
    }
    public static T DecelerationRatio<T>(this T target, double value) where T : Timeline {
      target.DecelerationRatio = value;
      return target;
    }
    public static T AccelerationRatio<T>(this T target, double value) where T : Timeline {
      target.AccelerationRatio = value;
      return target;
    }
    public static T Duration<T>(this T target, Duration value) where T : Timeline {
      target.Duration = value;
      return target;
    }
    public static T Duration<T>(this T target, TimeSpan value) where T : Timeline {
      target.Duration = new Duration(value);
      return target;
    }

    public static T Duration<T>(this T target, double value) where T : Timeline {
      return target.Duration((TimeSpan.FromMilliseconds(value)));
    }
    public static T Delay<T>(this T target, double value) where T : Timeline {
      target.BeginTime = TimeSpan.FromMilliseconds(value);
      return target;
    }
    public static T BeginTime<T>(this T target, TimeSpan timeSpan) where T : Timeline {
      target.BeginTime = timeSpan;
      return target;
    }
    /// <summary>
    /// Return a random point between (0,0) and (1,1).
    /// </summary>
    public static Point NextPoint(this Random r) {
      return new Point(r.NextDouble(), r.NextDouble());
    }

    public static T SpeedRatio<T>(this T target, double d) where T : Timeline {
      target.SpeedRatio = d;
      return target;
    }

    public static T BeginTime<T>(this T target, double value) where T : Timeline {
      return target.BeginTime(TimeSpan.FromMilliseconds(value));
    }
    public static T Repeat<T>(this T target, RepeatBehavior repeat) where T : Timeline {
      target.RepeatBehavior = repeat;
      return target;
    }
    public static T Forever<T>(this T target) where T : Timeline {
      return target.Repeat(RepeatBehavior.Forever);
    }
    public static T AutoReverse<T>(this T target, bool autoReverse) where T : Timeline {
      target.AutoReverse = autoReverse;
      return target;
    }
    public static T AutoReverse<T>(this T target) where T : Timeline {
      return target.AutoReverse(true);
    }

    public static T[] ToArray<T>(this MockArray<int, T> array) {
      var ret = new T[array.Count];
      for (int i = 0; i < array.Count; i++)
        ret[i] = array.Get(i);
      return ret;
    }
  }
}
